﻿// Copyright (c) Inossem. All rights reserved.
// Licensed under the LGPL-3.0 license. See LICENSE file in the project root for full license information.
using DWF.Studio.ScriptEngine.Custom;
using DWF.Studio.ScriptEngine.IPC.WorkFlow;
using DWF.Studio.Share.Serialization;
using System;
using System.Activities;
using System.Activities.Tracking;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DWF.Studio.ScriptEngine.Debuger
{
    /// <summary>
    /// 工作流调试器
    /// </summary>
    internal class WorkflowDebuger : IDisposable
    {
        /// <summary>
        /// 执行栈
        /// </summary>
        private Stack<ActivityStateRecord> _executeStack = null;

        private Stack<ActivityInfo> _scopeActivityStack = null;

        /// <summary>
        /// 单步堵塞
        /// </summary>
        private IThreadBlock _singleStepBlock = null;

        private WorkflowFrameInfo _currentFrameInfo = null;

        private BlockingCollection<WorkflowFrameInfo> _frameInfoQueue = null;

        private CancellationTokenSource _frameInfoQueueCancellation = null;

        private TrackingProfile _trackingProfile = null;

        private List<Breakpoint> _breakpoints = null;

        private string _projectDirectory = null;

        private string _scriptPath = null;

        public string CurrentScriptPath { get; private set; }

        private Custom.ExecutionContext _executionContext = null;

        public Action<WorkflowFrameInfo> FrameInfoTrack = null;

        public Action<WorkflowStepInfo> StepInfoTrack = null;

        private object _obj = null;

        public WorkflowDebuger(Custom.ExecutionContext executionContext)
        {
            _obj = new object();
            _breakpoints = new List<Breakpoint>();
            _executionContext = executionContext;
            _executeStack = new Stack<ActivityStateRecord>();
            _scopeActivityStack = new Stack<ActivityInfo>();
            _singleStepBlock = new AutoThreadBlock();
            _frameInfoQueue = new BlockingCollection<WorkflowFrameInfo>();
            _frameInfoQueueCancellation = new CancellationTokenSource();

            Task.Factory.StartNew(() =>
            {
                try
                {
                    WorkflowFrameInfo frameInfo;
                    while (_frameInfoQueue.TryTake(out frameInfo, Timeout.Infinite, _frameInfoQueueCancellation.Token))
                    {
                        if (frameInfo != null)
                        {
                            FrameInfoTrack?.Invoke(frameInfo);
                        }
                    }
                }
                catch
                {
                }
            }, TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// 设置TrackingProfile
        /// </summary>
        /// <param name="TrackingProfile"></param>
        public void SetTrackingProfile(TrackingProfile TrackingProfile)
        {
            _trackingProfile = TrackingProfile;
            _executeStack.Clear();
        }

        public void ReceiveTrackRecord(ActivityStateRecord stateRecord)
        {
            if (stateRecord.State == ActivityStates.Executing)
            {
                _executeStack.Push(stateRecord);//压栈
                setWorkflowFrameInfo(stateRecord);
            }
            else if (stateRecord.State == ActivityStates.Closed || stateRecord.State == ActivityStates.Faulted || stateRecord.State == ActivityStates.Canceled)
            {
                if (_executeStack.Count > 0 && _executeStack.Peek().Activity.Id == stateRecord.Activity.Id)
                {
                    _executeStack.Pop();
                }

                if (_scopeActivityStack.Count > 0 && _scopeActivityStack.Peek().Id == stateRecord.Activity.Id)
                {
                    _scopeActivityStack.Pop();

                    if (_executionContext.DebugStatus == EDebugStatus.DebugByStep)
                    {
                        var stepInfo = new WorkflowStepInfo() { ScriptPath = _scriptPath, Id = stateRecord.Activity.Id, Name = stateRecord.Activity.Name, TypeName = stateRecord.Activity.TypeName };
                        StepInfoTrack?.Invoke(stepInfo);
                        _singleStepBlock.Block();
                    }

                    setWorkflowFrameInfo(stateRecord);
                }
            }
        }

        private void setWorkflowFrameInfo(ActivityStateRecord stateRecord)
        {
            var variableinfos = new List<VariableInfo>();
            var argumentinfos = new List<ArgumentInfo>();

            foreach (var record in _executeStack.Reverse())//获取栈中的所有信息
            {
                var result = getArgumentsAndVariables(record);
                var arguments = result.Item1;
                foreach (var argument in arguments.Values)
                {
                    var jsonValue = String.Empty;
                    try
                    {
                        if (DebugSerializeTypeHandler.Validate(argument.Value?.GetType()))
                        {
                            jsonValue = JsonHelper.Serialize(argument.Value);
                        }
                    }
                    catch
                    {
                    };

                    argumentinfos.Add(new ArgumentInfo() { Name = argument.Name, ActivityName = argument.ActivityName, TypeName = argument.ValueType, Value = jsonValue });
                }

                var variables = result.Item2;
                foreach (var variable in variables.Values)
                {
                    var jsonValue = String.Empty;
                    try
                    {
                        if (DebugSerializeTypeHandler.Validate(variable.Value?.GetType()))
                        {
                            jsonValue = JsonHelper.Serialize(variable.Value);
                        }
                    }
                    catch
                    {
                    };

                    variableinfos.Add(new VariableInfo() { Name = variable.Name, ActivityName = variable.ActivityName, TypeName = variable.ValueType, Value = jsonValue });
                }
            }

            _currentFrameInfo = new WorkflowFrameInfo(stateRecord, variableinfos, argumentinfos);
        }

        private Tuple<Dictionary<string, dynamic>, Dictionary<string, dynamic>> getArgumentsAndVariables(ActivityStateRecord activityStateRecord)
        {
            var result = new Tuple<Dictionary<string, dynamic>, Dictionary<string, dynamic>>(new Dictionary<string, dynamic>(), new Dictionary<string, dynamic>());

            foreach (var argument in activityStateRecord.Arguments)
            {
                dynamic value = new ExpandoObject();
                value.Name = argument.Key;
                value.ActivityName = activityStateRecord.Activity.Name;
                value.ValueType = argument.Value?.GetType().FullName;
                value.Value = argument.Value;

                result.Item1.Add(argument.Key, value);
            }

            foreach (var variable in activityStateRecord.Variables)
            {
                dynamic value = new ExpandoObject();
                value.Name = variable.Key;
                value.ActivityName = activityStateRecord.Activity.Name;
                value.ValueType = variable.Value?.GetType().FullName;
                value.Value = variable.Value;

                result.Item2.Add(variable.Key, value);
            }

            var instanceProperty = typeof(ActivityInfo).GetProperty("Instance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var activityInfoInstance = instanceProperty != null ? instanceProperty.GetValue(activityStateRecord.Activity) : null;

            var environmentProperty = typeof(ActivityInstance).GetProperty("Environment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var environmentInstance = environmentProperty != null ? environmentProperty.GetValue(activityInfoInstance) : null;
            if (environmentInstance != null)
            {
                var locationsProperty = environmentInstance.GetType().GetProperty("SerializedLocations", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var locationsInstance = locationsProperty != null ? locationsProperty.GetValue(environmentInstance) : null;
                var locations = locationsInstance as Location[];

                var locationProperty = environmentInstance.GetType().GetProperty("SerializedSingleLocation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var locationInstance = locationProperty != null ? locationProperty.GetValue(environmentInstance) : null;
                var singleLocation = locationInstance as Location;

                var activityProperty = environmentInstance.GetType().GetProperty("System.Activities.Runtime.ActivityInstanceMap.IActivityReference.Activity", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var activityInstance = activityProperty != null ? activityProperty.GetValue(environmentInstance) : null;

                var argumentsProperty = activityInstance.GetType().GetProperty("RuntimeArguments", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var argumentsInstance = argumentsProperty != null ? argumentsProperty.GetValue(activityInstance) : null;
                var arguments = argumentsInstance as Collection<RuntimeArgument>;

                if (arguments != null && locations != null)
                {
                    for (int index = 0; index < arguments.Count; index++)
                    {
                        var arg = arguments[index];
                        var location = locations[index];

                        var key = arg.Name;
                        if (result.Item1.ContainsKey(key))
                        {
                            result.Item1[key].ValueType = arg.Type.FullName;
                            result.Item1[key].Value = location?.Value;
                        }
                        else
                        {
                            dynamic value = new ExpandoObject();
                            value.Name = arg.Name;
                            value.ActivityName = activityStateRecord.Activity.Name;
                            value.ValueType = arg.Type.FullName;
                            value.Value = location?.Value;

                            result.Item1.Add(key, value);
                        }
                    }
                }

                var variablesProperty = activityInstance.GetType().GetProperty("Variables", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var variablesInstance = variablesProperty != null ? variablesProperty.GetValue(activityInstance) : null;
                var variables = variablesInstance as Collection<Variable>;

                if (variables != null && locations != null && variables.Count <= locations.Length)
                {
                    for (int index = 0; index < variables.Count; index++)
                    {
                        var variable = variables[index];
                        var location = locations[index];

                        var key = variable.Name;
                        if (result.Item2.ContainsKey(key))
                        {
                            result.Item2[key].ValueType = variable.Type.FullName;
                            result.Item2[key].Value = location?.Value;
                        }
                        else
                        {
                            dynamic value = new ExpandoObject();
                            value.Name = variable.Name;
                            value.ActivityName = activityStateRecord.Activity.Name;
                            value.ValueType = variable.Type.FullName;
                            value.Value = location?.Value;

                            result.Item2.Add(key, value);
                        }
                    }
                }
                else if (variables != null && singleLocation != null && variables.Count == 1)
                {
                    for (int index = 0; index < variables.Count; index++)
                    {
                        var variable = variables[index];
                        var location = singleLocation;

                        var key = variable.Name;
                        if (result.Item2.ContainsKey(key))
                        {
                            result.Item2[key].ValueType = variable.Type.FullName;
                            result.Item2[key].Value = location?.Value;
                        }
                        else
                        {
                            dynamic value = new ExpandoObject();
                            value.Name = variable.Name;
                            value.ActivityName = activityStateRecord.Activity.Name;
                            value.ValueType = variable.Type.FullName;
                            value.Value = location?.Value;

                            result.Item2.Add(key, value);
                        }
                    }
                }

                var bodyProperty = activityInstance.GetType().GetProperty("Body", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var bodyInstance = bodyProperty != null ? bodyProperty.GetValue(activityInstance) : null;
                var argumenPropertyt = bodyInstance?.GetType().GetProperty("Argument", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var argument = argumenPropertyt?.GetValue(bodyInstance) as DelegateArgument;

                if (argument != null && locations != null)
                {
                    foreach (var location in locations)
                    {
                        var enumerator = location.Value as System.Collections.IEnumerator;
                        if (enumerator != null)
                        {
                            var key = argument.Name;
                            if (result.Item2.ContainsKey(key))
                            {
                                result.Item2[key].ValueType = argument.Type.FullName;
                                result.Item2[key].Value = enumerator.Current;
                            }
                            else
                            {
                                dynamic value = new ExpandoObject();
                                value.Name = argument.Name;
                                value.ActivityName = activityStateRecord.Activity.Name;
                                value.ValueType = argument.Type.FullName;
                                value.Value = enumerator.Current;

                                result.Item2.Add(key, value);
                            }

                            break;
                        }
                    }
                }
            }

            return result;
        }

        public void ReceiveTrackRecord(ActivityScheduledRecord scheduledRecord)
        {
            if (scheduledRecord.Activity.TypeName != "System.Activities.DynamicActivity" && (_scopeActivityStack.Count == 0 || _scopeActivityStack.Peek().Id != scheduledRecord.Activity.Id))
            {
                _scopeActivityStack.Push(scheduledRecord.Activity);
            }

            if (_executionContext.DebugStatus == EDebugStatus.DebugByStep)
            {
                var stepInfo = new WorkflowStepInfo() { ScriptPath = _scriptPath, Id = scheduledRecord.Child.Id, Name = scheduledRecord.Child.Name, TypeName = scheduledRecord.Child.TypeName };
                StepInfoTrack?.Invoke(stepInfo);
                _singleStepBlock?.Block();
            }
            else if (_executionContext.DebugStatus == EDebugStatus.Debug)
            {
                bool ishit;
                lock (_obj)
                {
                    ishit = _breakpoints.Any(item => item.ActivityType == scheduledRecord.Child.TypeName && item.ActivityId == scheduledRecord.Child.Id);
                }

                if (ishit)
                {
                    var stepInfo = new WorkflowStepInfo() { ScriptPath = _scriptPath, Id = scheduledRecord.Child.Id, Name = scheduledRecord.Child.Name, TypeName = scheduledRecord.Child.TypeName };
                    StepInfoTrack?.Invoke(stepInfo);
                    _singleStepBlock?.Block();
                }
            }
        }

        public void Resume()
        {
            _singleStepBlock?.Continue();
        }

        public void StepInto()
        {
        }

        public void StepOut()
        {
        }

        public void StepOver()
        {
            _singleStepBlock?.Continue();
        }

        public void CallFrameInfo()
        {
            if (_currentFrameInfo != null)
            {
                _frameInfoQueue?.TryAdd(_currentFrameInfo);
            }
        }

        public void LoadBreakpoints()
        {
            lock (_obj)
            {
                _breakpoints.Clear();
                var breakpoints = BreakpointsHandler.GetBreakpoints(_projectDirectory, _scriptPath); ;
                if (breakpoints != null)
                {
                    _breakpoints.AddRange(breakpoints.Where(item => item.BreakpointType == 1));
                }
            }
        }

        public void SetPaths(string projectDirectory, string scriptPath)
        {
            _projectDirectory = projectDirectory;
            _scriptPath = scriptPath;
            CurrentScriptPath = scriptPath;
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。

                    _executeStack.Clear();
                    _scopeActivityStack.Clear();
                    _singleStepBlock.Dispose();

                    _trackingProfile = null;
                    _executeStack = null;
                    _scopeActivityStack = null;
                    _singleStepBlock = null;

                    _frameInfoQueueCancellation.Cancel();
                    _frameInfoQueue.Dispose();
                    _frameInfoQueue = null;
                    _frameInfoQueueCancellation.Dispose();
                    _frameInfoQueueCancellation = null;
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~WorkflowDebuger()
        // {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
